08.4 精通自定义 View 之 混合模式——目标图像模式与其他模式

返回自定义 View 目录

8.4.1 目标图像模式

我们知道,在与 SRC 相关的模式中,在处理相交区域时,优先以源图像显示为主;而在与 DST 相关的模式中,在处理相交区域时,优先以目标图像显示为主。这部分所涉及的模式有 Mode.DST、Mode.DST_IN、Mode.DST_OUT、Mode.DST_OVER、Mode.DST_ATOP。

1. Mode.DST

计算公式:[Da, Dc]。
从公式中也可以看出,在处理源图像所在区域的相交问题时,正好与 Mode.SRC 模式相反,全部以目标图像显示。示例图像如下图所示。

2. Mode.DST_IN

1)概述

计算公式为:[Da * Sa, Dc * Sa]。

将这个公式与 Mode.SRC_IN 的公式([Sa * Da, Sc * Da])对比一下,发现正好与 SRC_IN 相反,Mode.DST_IN 是在相交时利用源图像的透明度来改变目标图像的透明度和饱和度。当源图像透明度为 0时,目标图像就完全不显示。示例图像为:

利用 SRC 模式能实现的效果,只需要将源图像与目标图像对调,利用对应的 DST 模式就可以实现同样的效果。比如,在 8.3 节中所实现的圆角效果,它对应的 DST 模式的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TestView extends View {
private Paint mPaint;
private Bitmap bmpDST, bmpSRC;
private PorterDuffXfermode mMode;
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
bmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.dog,null);
bmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.dog_shade,null);
mMode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(bmpDST,0,0,mPaint);
mPaint.setXfermode(mMode);
canvas.drawBitmap(bmpSRC,0,0,mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
}

很明显,这里只改了两部分代码:首先,在解析图片时,将要显示的小狗图像作为目标图像,将控制哪部分显示的遮罩图像作为源图像;其次,将合成模式改为 Mode.DST_IN。其效果与 8.3 节中圆角效果一致。

2)示例:区域波纹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
public class TestView extends View {
private Paint mPaint;
private Canvas mTempCanvas;
// 用来生成波纹
private Path mPath;
// 波纹图像为目标图像,要显示的内容
private Bitmap dstBmp;
// 文字图像为源图像
private Bitmap srcBmp;
// 波纹动画移动的距离
private int mDx;
// 波纹下降动画的位移
private int mDy;
// 一个波长
private int mItemWaveLength = 1000;
private PorterDuffXfermode mMode;
private String mText;
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mMode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
mPath = new Path();
mTempCanvas = new Canvas();
mText = "先小涛";
srcBmp = makeTextBitmap();
dstBmp = Bitmap.createBitmap(srcBmp.getWidth(), srcBmp.getHeight(), Bitmap.Config.ARGB_8888);
startAnim();
}
/**
* 开启动画
*/
public void startAnim() {
// 波纹动画
ValueAnimator waveAnimator = ValueAnimator.ofInt(0, mItemWaveLength);
waveAnimator.setDuration(2000);
waveAnimator.setRepeatCount(ValueAnimator.INFINITE);
waveAnimator.setInterpolator(new LinearInterpolator());
waveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDx = (Integer) animation.getAnimatedValue();
postInvalidate();
}
});
// 下降动画
ValueAnimator downAnimator = ValueAnimator.ofInt(0, srcBmp.getHeight());
downAnimator.setDuration(8000);
downAnimator.setRepeatCount(ValueAnimator.INFINITE);
downAnimator.setInterpolator(new LinearInterpolator());
downAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDy = (Integer) animation.getAnimatedValue();
}
});
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(waveAnimator).with(downAnimator);
animatorSet.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 将生成的波纹绘制到空白区域
generateWavePath();
mTempCanvas.setBitmap(dstBmp);
mTempCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
mTempCanvas.drawPath(mPath, mPaint);
// 先绘制文字,再绘制合成效果
canvas.drawBitmap(srcBmp, 0, 0, mPaint);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(dstBmp, 0, 0, mPaint);
mPaint.setXfermode(mMode);
canvas.drawBitmap(srcBmp, 0, 0, mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
/**
* 将字符串转化成 bitmap
* @return Bitmap
*/
private Bitmap makeTextBitmap() {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setTextSize(200);
paint.setColor(Color.WHITE);
paint.setTextAlign(Paint.Align.LEFT);
Paint.FontMetricsInt fm = paint.getFontMetricsInt();
int width = (int) paint.measureText(mText);
int height = fm.descent - fm.ascent;
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawText(mText, 0, fm.leading - fm.ascent, paint);
canvas.save();
return bitmap;
}
/**
* 生成波纹
*/
private void generateWavePath() {
mPath.reset();
// int originY = srcBmp.getHeight() / 2;
int originY = mDy;
int halfWaveLen = mItemWaveLength / 2;
mPath.moveTo(-mItemWaveLength + mDx, originY);
for (int i = -mItemWaveLength; i <= getWidth() + mItemWaveLength; i+= mItemWaveLength) {
mPath.rQuadTo(halfWaveLen / 2f, -50, halfWaveLen, 0);
mPath.rQuadTo(halfWaveLen / 2f, 50, halfWaveLen, 0);
}
mPath.lineTo(srcBmp.getWidth(), srcBmp.getHeight());
mPath.lineTo(0, srcBmp.getHeight());
mPath.close();
}
}
3)示例:区域不规则波纹

3. Mode.DST_OUT

计算公式为:[Da * (1 - Sa), Dc * (1 - Sa)]。

将这个公式与 Mode.SRC_OUT 的公式([Sa * (1 - Da), Sc * (1 - Da)])对比一下可以看出,Mode.SRC_OUT 是利用目标图像的透明度的补值来改变源图像的透明度和饱和度。而 Mode.DST_OUT 反过来,是通过源图像的透明度补值来改变目标图像的透明度和饱和度。

简单来说,在 Mode.DST_OUT 模式下,就是相交区域显示的是目标图像,目标图像的透明度和饱和度与源图像的透明度相反,当源图像透明底是 100% 时,则相交区域为空值。当源图像透明度为 0 时,则完全显示目标图像。非相交区域完全显示目标图像。示例图像如下图所示。

用下图的分解图来讲解一下这个效果的生成方式,如下图所示。

图中区域一的相交区域:在 DST_OUT 模式下,由于源图像的透明度是 100%,所以计算后的结果图像在这个区域是空白像素。

图中区域二的非相交区域:在 DST_OUT 模式下,这个区域的源图像透明度仍为 100%,所以计算后的结果图像在这个区域仍是空白像素。

所以,当源图像区域透明度为 100%时,所在区域计算结果为透明像素,当源图像的区域透明时,计算结果就是目标图像。

这与 SRC_OUT 模式的结果正好相反,在 SRC_OUT 模式下,当目标图像区域透明度为 100% 时,所在区域计算结果为透明像素;当目标图像的区域透明时,非相交区域的计算结果就是源图像。

所以,在 8.3.3 节中使用 SRC_OUT 模式实现的橡皮擦效果和刮刮卡效果都是可以使用 DST_OUT 模式实现的,只需要将 SRC 和 DST 所对应的图像翻转一下就可以了。

4. Mode.DST_OVER

计算公式为:[Sa + (1 - Sa) * Da, Rc = Dc + (1 - Da) * Sc]。

同样先与 Mode.SRC_OVER 的公式([Sa + (1 - Sa) * Da, Rc = Sc + (1 - Sa) * Dc])对比一下,可以看出,从 SRC 模式中以显示 SRC 图像为主变成了以显示 DST 图像为主,从 SRC 模式中的使用目标图像控制结果图像的透明度和饱和度变成了由源图像控件结果图像的透明度和饱和度。

5. Mode.DST_ATOP

计算公式为:[Sa, Sa * Dc + Sc * (1 - Da)]。

在 SRC 中,一般而言,SRC_ATOP 是可以和 SRC_IN 通用的,但 SRC_ATOP 所产生的效果图在目标图的透明度不是 0 或 100% 的时候,会比 SRC_IN 模式产生的效果图更亮。

我们再来对比下 DST 中的两种模式与 SRC 中的这两种模式的公式的区别:

模式 公式
SRC_IN [Sa * Da, Sc * Da]
SRC_ATOP [Da, Sc * Da + (1 - Sa) * Dc]
DST_IN [Da * Sa, Dc * Sa ]
DST_ATOP [Sa, Sa * Dc + Sc * (1 - Da)]

从公式中可以看到,在 SRC 模式中,以显示源图像为主,透明度和饱和度利用 Da 来调节;而在 DST 模式中,以显示目标图像为主,透明度和饱和度利用 Sa 来调节。

所以,Mode.DST_ATOP 与 Mode.DST_IN 的关系也是:一般而言,DST_ATOP 是可以和 DST_IN 通用的,但 DST_ATOP 所产生的效果图在源图像的透明度不是 0 或 100% 的时候,会比 DST_IN 模式产生的效果图更亮。

同样,使用 Mode.DST_ATOP 也可以实现 8.3.2 节中利用 Mode.SRC_ATOP 所实现的两个示例:圆角效果和图片倒影,这里就不再讲了。

到这里,有关 DST 相关模式都讲完了,我们总结一下:

  • DST 相关模式是完全可以使用 SRC 对应的模式来实现的,只需将目标图像和源图像对调一下即可。
  • 在 SRC 模式中,是以显示源图像为主,通过目标图像的透明度来调节计算结果的透明度和饱和度,而在 DST 模式中,是以显示目标图像为主,通过源图像的透明度来调节计算结果的透明度和饱和度。

8.4.2 其他模式

计算公式:[0, 0]。

从公式中可以看到,计算结果直接就是[0,0],即空白像素。也就是说,源图像所在区域都会变成空像素,这样就起到了清空源图像所在区域图像的作用。

8.4.3 模式总结

在实际应用中,我们可以从以下三个方面来决定使用哪种模式。

  • 目标图像和源图像混合,需不需要生成颜色的叠加特效。如果需要,则从颜色叠加相关模式中选择,有 Mode.ADD(饱和度相加)、Mode.DARKEN(变暗)、Mode.LIGHTEN(变亮)、Mode.MULTIPLY(正片叠底)、Mode.OVERLAY(叠加)、Mode.SCREEN(滤色)。
  • 当不需要特效,而需要根据某张图片的透明像素来裁剪时,就需要使用 SRC 相关或 DST 相关模式了。而 SRC 相关模式与 DST 相关模式是相通的,唯一不同的是决定当前哪个图像是目标图像和源图像。
  • 当需要清空图像时,使用 Mode.CLEAR 模式。